from CvPythonExtensions import *

import GerikSpells
from GerikSpells import GerikSpellBase
from GSGroupPolicies import GSAll as GSGroup
from GSHandlerPolicies import GSTargetting as GSHandler
from GSResultsPolicies import GSMission as GSResults

from GSTargetInfoPolicies import GSSinglePlotTarget
from GSTargetInfoPolicies import GSSpecificUnitTarget

from GSGraphicsPolicies import GSPlot
from GSGraphicsPolicies import GSPieMenu

import GerikSpellsUtils

import CivcraftSCCombat as Combat

gc = CyGlobalContext()

class AttackBase(GerikSpellBase, GSGroup, GSHandler):
    def __init__(self):
        GerikSpellBase.__init__(self)
        GSGroup.__init__(self)
        GSHandler.__init__(self)

    def canUnitCast(self, pCastingUnit, pPlot, bVisible):
        if not pCastingUnit.canSCAttack(pPlot):
            return False

        if bVisible:
            return True

        if not pCastingUnit.hasMoreAttacksLeft():
            return False

        return True
            

    def canTargetPlot(self, pCastingUnit, pPlot, pTargetPlot, bVisible):
        return True

    def createTargettingStageList(self, bAlt, bShift, bCtrl):
        pPlotTargetPolicy = GSSinglePlotTarget(self.canTargetPlot)
        pPlotTargetPolicy.setRangeMakerCallback(self.getRangePlotList)
        pPlotTargetPolicy.setCheckRange(True)

        pPlotGraphicsPolicy = GSPlot()
        pPlotGraphicsPolicy.setShowRangeGraphics(False)
        pPlotGraphicsPolicy.setCursorType("CURSOR_SINGLE_ATTACK")
        return [(pPlotTargetPolicy, pPlotGraphicsPolicy)]

    def getRangePlotList(self, pCastingPlot):
        return GerikSpellsUtils.getRangePlotDistanceList(pCastingPlot, self.getCastingGroup().getMaxRange() + 1)

class NormalAttackBase(AttackBase, GSResults):
    def __init__(self):
        AttackBase.__init__(self)
        GSResults.__init__(self, GSSinglePlotTarget())

    def startSpellResultsOnTarget(self, pCastingGroup, pTarget):
        CyEngine().triggerEffect(40, pTarget.getPoint())
        

class TargettedAttackBase(AttackBase, GSResults):
    def __init__(self):
        AttackBase.__init__(self)
        GSResults.__init__(self, GSSpecificUnitTarget(self.canTargetUnit))

    def createTargettingStageList(self, bAlt, bShift, bCtrl):
        stageList = AttackBase.createTargettingStageList(self, bAlt, bShift, bCtrl)

        # Second stage. Get a unit.
        pUnitTargetPolicy = GSSpecificUnitTarget(self.canTargetUnit)
        pUnitMenuPolicy = GSPieMenu()
        stageList.append( (pUnitTargetPolicy, pUnitMenuPolicy) )

        return stageList

    def canTargetUnit(self, pCastingGroup, pCastingPlot, pTargetUnit, bVisible):
        for pUnit in pCastingGroup.getUnits():
            if pUnit.canSCAttackUnit(pTargetUnit, False):
                return True    

#
# Now the actual spell classes.
#


#
# Single Attack
#
class SingleAttack(NormalAttackBase):
    def __init__(self):
        NormalAttackBase.__init__(self)

    def startSpellResultsOnTarget(self, pCastingGroup, pTargetPlot):
        bCombat = False
        for pUnit in pCastingGroup.getUnits():
            if Combat.singleAttack(pUnit, pTargetPlot):
                bCombat = True

        if bCombat:
            CyEngine().triggerEffect(40, pTargetPlot.getPoint())

        return False

#
# Single Attack Targetted
#
class SingleAttackTargetted(TargettedAttackBase):
    def __init__(self):
        TargettedAttackBase.__init__(self)

    def startSpellResultsOnTarget(self, pCastingGroup, pTargetUnit):
        bCombat = False
        for pUnit in pCastingGroup.getUnits():
            if Combat.singleAttackTargetted(pUnit, pTargetUnit):
                bCombat = True
            if pTargetUnit.isDead():
                break

        if bCombat:
            CyEngine().triggerEffect(40, pTargetUnit.plot().getPoint())
        return False

#
# Turn Attack
#
class TurnAttack(NormalAttackBase):
    def __init__(self):
        NormalAttackBase.__init__(self)

    def startSpellResultsOnTarget(self, pCastingGroup, pTargetPlot):
        bCombat = False
        for pUnit in pCastingGroup.getUnits():
            if Combat.turnAttack(pUnit, pTargetPlot):
                bCombat = True

        if bCombat:
            CyEngine().triggerEffect(40, pTargetPlot.getPoint())

        return False

#
# Turn Attack Targetted
#
class TurnAttackTargetted(TargettedAttackBase):
    def __init__(self):
        TargettedAttackBase.__init__(self)

    def startSpellResultsOnTarget(self, pCastingGroup, pTargetUnit):
        bCombat = False
        for pUnit in pCastingGroup.getUnits():
            if Combat.turnAttackTargetted(pUnit, pTargetUnit):
                bCombat = True
            if pTargetUnit.isDead():
                break

        if bCombat:
            CyEngine().triggerEffect(40, pTargetUnit.plot().getPoint())
        return False

def isNotValid(x):
    return not x or x.isNone()

def createNewGroup(pParticipantUnits):
    if not pParticipantUnits:
        return None

    iLength = len(pParticipantUnits)
    if iLength == 0:
        return None
    
    pHeadUnit = pParticipantUnits[0]
    pHeadUnit.joinGroup(None, True, True)
    pNewGroup = pHeadUnit.getGroup()
    # Place rest of units (if any) that can't attack into this group.
    map(lambda x: x.joinGroup(pNewGroup, True, True), pParticipantUnits[1:])

    return pNewGroup

#
# Desroy Attack
#
#GERIKESTODO: This is a work in progress, so I'm making it not callable.
class DestroyAttack(NormalAttackBase):
    def __init__(self):
        NormalAttackBase.__init__(self)

    # GERIKESTODO: Remove this when ready to use.
    def canUnitCast(self, pCastingUnit, pCastingPlot, bVisible):
        return False


    def startSpellResultsOnTarget(self, pCastingGroup, pTargetPlot):
        """
        Gets all the units that cannot attack any unit in the plot, and puts them in a seperate group.
        Then, gets targets for each unit. A group is created that has all the units that are going to start
        destroy mode on that one target. Then, for each group, the group gets pushed the destroyAttackTargetted mission
        with that target.
        """
        # Don't do anything if there are no targets to pick from.
        if pTargetPlot.getNumVisibleEnemyDefenders(pCastingGroup.getOwner()) == 0:
            return False

        # List of units that will be seperated out, since there are no units that they can attack.
        pCannotAttackUnits = []

        # Dictionary of target (actually, their ID's) and the list of units that will be attacking them.
        pTargetAttackers = {}

        for pLoopUnit in pCastingGroup.getUnits():
            pTargetUnit = pTargetPlot.getBestDefenderSC(pLoopUnit, True)
            if isNotValid(pTargetUnit):
                pCannotAttackUnits.append(pLoopUnit)
            else:
                tTargetID = (pTargetUnit.getOwner(), pTargetUnit.getID())
                if pTargetAttackers.has_key(tTargetID):
                    pTargetAttackers[tTargetID].append(pLoopUnit)
                else:
                    pTargetAttackers[tTargetID] = [pLoopUnit]
        
        # Get all the units that can't attack and give them their own group.
        if len(pCannotAttackUnits) > 0:
            createNewGroup(pCannotAttackUnits)

        # Get all the units that can attack and put them into their own groups.
        # Then, tell that group to destroy attack the target with the targetted destroy attack spell.
        for tTargetInfo in pTargetAttackers.keys():
            pTarget = gc.getPlayer(tTargetInfo[0]).getUnit(tTargetInfo[1])
            pAttackers = pTargetAttackers[tTargetInfo]
            pNewGroup = createNewGroup(pAttackers)
            pNewGroup.pushSpellMission(gc.getInfoTypeForString("SPELL_DESTROY_ATTACK_TARGETTED"), tTargetInfo[0], tTargetInfo[1], -1, True)

        return False




#
# Destroy Attack Targetted
#
class DestroyAttackTargetted(TargettedAttackBase):
    def __init__(self):
        TargettedAttackBase.__init__(self)

    def startSpellResultsOnTarget(self, pCastingGroup, pTarget):
        if isNotValid(pTarget):
            return False

        if pTarget.isDead():
            return False

        # Continue the mission for the remaining units.
        return True

    def continueSpellResultsOnTarget(self, pCastingGroup, pTargetUnit):
        if isNotValid(pTargetUnit):
            return False

        if pTargetUnit.isDead():
            return False

        pUnitsCannotAttack = []
        pNeedToMoveUnits = []
        
        for pLoopUnit in pCastingGroup.getUnits():
            if not pLoopUnit.canSCAttackUnit(pTargetUnit, True):
                pUnitsCannotAttack.append(pLoopUnit)
            elif not pLoopUnit.isUnitWithinSCAttackRange(pTargetUnit):
                pNeedToMoveUnits.append(pLoopUnit)
        del pLoopUnit


        
        iNumUnitsLeftToAttack = pCastingGroup.getNumUnits()

        iNumUnitsLeavingCannotAttack = len(pUnitsCannotAttack)
        if iNumUnitsLeavingCannotAttack == iNumUnitsLeftToAttack:
            return False
        else:
            createNewGroup(pUnitsCannotAttack)
            iNumUnitsLeftToAttack -= iNumUnitsLeavingCannotAttack
        
        iNumUnitsLeavingToMove = len(pNeedToMoveUnits)
        if iNumUnitsLeavingToMove == iNumUnitsLeftToAttack:
            # Move the entire stack
            iNumMoves = pCastingGroup.generatePathReturnModified(pCastingGroup.plot(), pTargetUnit.plot(), -1, True)
            if iNumMoves <= 0:
                # Can't find path to target.
                return False
            else:
                pFirstPlot = pCastingGroup.getPathFirstPlot()
                pCastingGroup.pushMoveToMission(pFirstPlot.getX(), pFirstPlot.getY())
                pCastingGroup.pushSpellMission(self.getSpellNumber(), pTargetUnit.getOwner(), pTargetUnit.getID(), -1, True)
                return False
        else:
            # Create a new group for these units and push this mission.
            if iNumUnitsLeavingToMove > 0:
                pNewGroup = createNewGroup(pNeedToMoveUnits)
                pNewGroup.pushSpellMission(self.getSpellNumber(), pTargetUnit.getOwner(), pTargetUnit.getID(), -1, False)


        # With the final units, do an attack:
        for pLoopUnit in pCastingGroup.getUnits():
            Combat.turnAttackTargetted(pLoopUnit, pTargetUnit)
            pLoopUnit.finishMoves()
            if pTargetUnit.isDead():
                return False
            

        return True

        

        

                        
            
        
            
            
